package com.example.iot.service.core.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.example.iot.dao.RuleChainDao;
import com.example.iot.dao.RuleNodeDao;
import com.example.iot.dao.RuleRelationDao;
import com.example.iot.entity.EntityRelation;
import com.example.iot.entity.RuleChain;
import com.example.iot.entity.RuleNode;
import com.example.iot.entity.dto.NodeConnectionInfo;
import com.example.iot.entity.dto.RuleChainMetaData;
import com.example.iot.entity.po.RelationPO;
import com.example.iot.entity.po.RuleChainPO;
import com.example.iot.entity.po.RuleNodePO;
import com.example.iot.entity.id.RuleChainId;
import com.example.iot.entity.id.RuleNodeId;
import com.example.iot.service.core.RuleChainService;
import com.example.iot.util.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ChenXiangpeng
 * @since 2021-02-26
 */
@Service
@Slf4j
public class RuleChainServiceImpl implements RuleChainService {
    @Autowired
    private RuleChainDao ruleChainDao;
    @Autowired
    private RuleNodeDao ruleNodeDao;
    @Autowired
    private RuleRelationDao ruleRelationDao;

    @Override
    public RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId) {
        RuleChain ruleChain = ruleChainDao.findRuleChainById(ruleChainId)
                .orElseThrow(() -> new RuntimeException("规则链不存在"))
                .toDto();

        RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
        ruleChainMetaData.setRuleChainId(ruleChainId);
        List<RuleNode> ruleNodes = ruleNodeDao.getRuleChainNodes(ruleChainId).stream().map(RuleNodePO::toDto).collect(Collectors.toList());

        Map<RuleNodeId, Integer> ruleNodeIndexMap = new HashMap<>();
        for (RuleNode node : ruleNodes) {
            ruleNodeIndexMap.put(node.getId(), ruleNodes.indexOf(node));
        }
        ruleChainMetaData.setNodes(ruleNodes);
        if (ruleChain.getFirstRuleNodeId() != null) {
            ruleChainMetaData.setFirstNodeIndex(ruleNodeIndexMap.get(ruleChain.getFirstRuleNodeId()));
        }
        for (RuleNode node : ruleNodes) {
            List<EntityRelation> nodeRelations = ruleRelationDao.getRuleNodeRelations(node.getId()).stream().map(RelationPO::toDTO).collect(Collectors.toList());
            for (EntityRelation nodeRelation : nodeRelations) {
                Integer fromIndex = ruleNodeIndexMap.get(node.getId());
                Integer toIndex = ruleNodeIndexMap.get(nodeRelation.getTo());
                String type = nodeRelation.getType();
                ruleChainMetaData.addConnectionInfo(node.getId(), fromIndex, nodeRelation.getTo(), toIndex, type);
            }
        }
        return ruleChainMetaData;
    }

    @Override
    public void saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) {
        RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId();
        Optional<RuleChainPO> ruleChainById = ruleChainDao.findRuleChainById(ruleChainId);
        RuleChainPO ruleChain = ruleChainById
                .orElseThrow(() -> {
                    log.debug("规则链不存在");
                    return new RuntimeException("规则链不存在");
                });

        // 验证连接是否存在闭环死循环
        if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
            validateCircles(ruleChainMetaData.getConnections());
        }

        List<RuleNodePO> nodes = ruleChainMetaData.getNodes().stream().map(RuleNodePO::ofDto).collect(Collectors.toList());
        // todo 暂时直接删除后重建
        ruleNodeDao.deleteByChainId(ruleChainId);
        ruleNodeDao.saveBatch(nodes);


        List<RuleNode> existingRuleNodes = ruleNodeDao.getRuleChainNodes(ruleChainMetaData.getRuleChainId())
                .stream().map(RuleNodePO::toDto).collect(Collectors.toList());
        for (RuleNode existingNode : existingRuleNodes) {
            // 删除全部节点关系
            ruleRelationDao.deleteRelations(existingNode.getId());
        }
        /*List<RuleNode> toAddOrUpdate = new ArrayList<>();
        List<RuleNode> toDelete = new ArrayList<>();

        // 筛选出要增加修改和删除的节点集合
        Map<RuleNodeId, Integer> ruleNodeIndexMap = new HashMap<>();
        if (nodes != null) {
            for (RuleNode node : nodes) {
                // id为空则表示需要新增
                if (node.getId() != null) {
                    ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node));
                } else {
                    toAddOrUpdate.add(node);
                }
            }
        }

        List<RuleNode> existingRuleNodes = ruleNodeDao.getRuleChainNodes(ruleChainMetaData.getRuleChainId())
                .stream().map(RuleNodePO::toDto).collect(Collectors.toList());
        for (RuleNode existingNode : existingRuleNodes) {
            // 删除全部节点关系
            ruleRelationDao.deleteRelations(existingNode.getId());
            Integer index = ruleNodeIndexMap.get(existingNode.getId());
            if (index != null) {
                toAddOrUpdate.add(ruleChainMetaData.getNodes().get(index));
            } else {
                toDelete.add(existingNode);
            }
        }

        for (RuleNode node : toAddOrUpdate) {
            node.setRuleChainId(ruleChain.getId());
            ruleNodeDao.saveOrUpdate(RuleNodePO.ofDto(node));

            int index = nodes.indexOf(node);
            nodes.set(index, node);
            ruleNodeIndexMap.put(node.getId(), index);
        }
        for (RuleNode node : toDelete) {
            ruleNodeDao.deleteRuleNode(node.getId());
        }*/
        UUID firstRuleNodeId = null;
        if (ruleChainMetaData.getFirstNodeIndex() != null) {
            firstRuleNodeId = UUIDUtil.fromString(nodes.get(ruleChainMetaData.getFirstNodeIndex()).getUuid());
        }
        if ((ruleChain.getFirstNodeId() != null && !ruleChain.getFirstNodeId().equals(firstRuleNodeId))
                || (ruleChain.getFirstNodeId() == null && firstRuleNodeId != null)) {
            ruleChain.setFirstNodeId(firstRuleNodeId);
            ruleChainDao.saveOrUpdate(ruleChain);
            //ruleChainMapper.insert(RuleChainPO.ofDto(ruleChain));
        }
        if (ruleChainMetaData.getConnections() != null) {
            for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) {
                UUID from = UUIDUtil.fromString(nodes.get(nodeConnection.getFromIndex()).getUuid());
                UUID to = UUIDUtil.fromString(nodes.get(nodeConnection.getToIndex()).getUuid());
                try {
                    ruleRelationDao.save(new RelationPO(null, from, to, "RULE_NODE", nodeConnection.getType()));
                } catch (Exception e) {
                    log.warn("Failed to create rule node relation. from: [{}], to: [{}]", from, to);
                    throw new RuntimeException(e);
                }
            }
        }
        /*if (ruleChainMetaData.getRuleChainConnections() != null) {
            for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) {
                EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId();
                EntityId to = nodeToRuleChainConnection.getTargetRuleChainId();
                String type = nodeToRuleChainConnection.getType();
                try {
                    createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo()));
                } catch (ExecutionException | InterruptedException e) {
                    log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to);
                    throw new RuntimeException(e);
                }
            }
        }*/

    }

    @Override
    public RuleChain findRuleChainById(RuleChainId ruleChainId) {
        Optional<RuleChainPO> ruleChainById = ruleChainDao.findRuleChainById(ruleChainId);
        return ruleChainById
                .orElseThrow(() -> new RuntimeException("规则链不存在"))
                .toDto();
    }

    @Override
    public List<RuleNode> getRuleChainNodes(RuleChainId entityId) {
        return ruleNodeDao.getRuleChainNodes(entityId)
                .stream().map(RuleNodePO::toDto).collect(Collectors.toList());
    }

    @Override
    public List<EntityRelation> getRuleNodeRelations(RuleNodeId id) {
        return ruleRelationDao.getRuleNodeRelations(id).stream().map(RelationPO::toDTO).collect(Collectors.toList());

    }

    @Override
    public RuleNode findRuleNodeById(RuleNodeId entityId) {
        return ruleNodeDao.findRuleNodeById(entityId).orElse(new RuleNodePO()).toDto();
    }

    private void validateCircles(List<NodeConnectionInfo> connectionInfos) {
        Map<Integer, Set<Integer>> connectionsMap = new HashMap<>();
        for (NodeConnectionInfo nodeConnection : connectionInfos) {
            if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) {
                throw new RuntimeException("Can't create the relation to yourself.");
            }
            connectionsMap
                    .computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>())
                    .add(nodeConnection.getToIndex());
        }
        connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap));
    }

    private void validateCircles(int from, Set<Integer> toList, Map<Integer, Set<Integer>> connectionsMap) {
        if (toList == null) {
            return;
        }
        for (Integer to : toList) {
            if (from == to) {
                throw new RuntimeException("Can't create circling relations in rule chain.");
            }
            validateCircles(from, connectionsMap.get(to), connectionsMap);
        }
    }
}
